<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebSocket 二进制客户端 (带验证)</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 0;
            padding: 20px;
            background-color: #f5f5f5;
        }
        .container {
            max-width: 800px;
            margin: 0 auto;
            background-color: white;
            border-radius: 5px;
            box-shadow: 0 2px 5px rgba(0,0,0,0.1);
            padding: 20px;
        }
        h1 {
            color: #333;
            text-align: center;
            margin-top: 0;
        }
        .connection-form {
            display: flex;
            flex-wrap: wrap;
            gap: 10px;
            align-items: center;
            margin-bottom: 20px;
            padding: 15px;
            background-color: #f9f9f9;
            border-radius: 5px;
        }
        .connection-form label {
            margin-right: 5px;
        }
        .connection-form input {
            padding: 8px;
            border: 1px solid #ddd;
            border-radius: 4px;
            flex: 1;
            min-width: 100px;
        }
        button {
            padding: 8px 15px;
            background-color: #4CAF50;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
        }
        button:hover {
            background-color: #45a049;
        }
        button:disabled {
            background-color: #cccccc;
            cursor: not-allowed;
        }
        .status {
            padding: 5px 10px;
            border-radius: 4px;
            font-weight: bold;
        }
        .connected {
            background-color: #dff0d8;
            color: #3c763d;
        }
        .disconnected {
            background-color: #f2dede;
            color: #a94442;
        }
        .message-container {
            height: 300px;
            overflow-y: auto;
            border: 1px solid #ddd;
            border-radius: 4px;
            padding: 10px;
            margin-bottom: 20px;
            background-color: #fff;
        }
        .message {
            margin-bottom: 8px;
            padding: 8px;
            border-radius: 4px;
        }
        .system {
            background-color: #f8f9fa;
            color: #6c757d;
        }
        .received {
            background-color: #e8f4f8;
            color: #0275d8;
        }
        .sent {
            background-color: #e8f8e8;
            color: #5cb85c;
        }
        .message-form {
            display: flex;
            gap: 10px;
        }
        .message-form input {
            flex: 1;
            padding: 8px;
            border: 1px solid #ddd;
            border-radius: 4px;
        }
        select {
            padding: 8px;
            border: 1px solid #ddd;
            border-radius: 4px;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>WebSocket 二进制客户端 (带验证)</h1>

        <div class="connection-form">
            <label for="appid">应用ID:</label>
            <select id="appid">
                <option value="app1">app1</option>
                <option value="app2">app2</option>
            </select>

            <label for="uid">用户ID:</label>
            <input type="text" id="uid" placeholder="输入用户ID">

            <label for="token">Token:</label>
            <input type="text" id="token" placeholder="输入Token">

            <button id="connect-btn">连接</button>
            <button id="disconnect-btn" disabled>断开连接</button>
            <span id="status" class="status disconnected">未连接</span>
        </div>

        <div class="message-container" id="message-container"></div>

        <div class="message-form">
            <input type="text" id="message" placeholder="输入消息" disabled>
            <select id="message-format" disabled>
                <option value="binary">二进制格式</option>
                <option value="json">JSON格式</option>
            </select>
            <button id="send-btn" disabled>发送到news主题</button>
        </div>

        <div style="margin-top: 20px; padding: 10px; background-color: #f8f9fa; border-radius: 4px;">
            <h3>验证说明:</h3>
            <p><strong>app1</strong>: Token 必须是用户ID的反转 (例如: 用户ID为 "user1", token应为 "1resu")</p>
            <p><strong>app2</strong>: Token 必须是 "app2-" 加上用户ID (例如: 用户ID为 "user1", token应为 "app2-user1")</p>
        </div>
    </div>

    <script>
        // 二进制消息类型
        const MessageType = {
            TEXT: 1,
            SUBSCRIBE: 2,
            UNSUBSCRIBE: 3,
            PING: 4,
            PONG: 5
        };

        // 编码二进制消息
        function encodeBinaryMessage(type, content = "", topic = "") {
            // 计算消息总长度
            // 1字节类型 + 4字节内容长度 + 内容字节数 + 4字节主题长度 + 主题字节数
            const contentBytes = new TextEncoder().encode(content);
            const topicBytes = new TextEncoder().encode(topic);
            const totalLength = 1 + 4 + contentBytes.length + 4 + topicBytes.length;

            // 创建二进制缓冲区
            const buffer = new ArrayBuffer(totalLength);
            const view = new DataView(buffer);
            let offset = 0;

            // 写入消息类型 (1字节)
            view.setUint8(offset, type);
            offset += 1;

            // 写入内容长度 (4字节)
            view.setUint32(offset, contentBytes.length, true);
            offset += 4;

            // 写入内容
            const contentView = new Uint8Array(buffer, offset, contentBytes.length);
            contentView.set(contentBytes);
            offset += contentBytes.length;

            // 写入主题长度 (4字节)
            view.setUint32(offset, topicBytes.length, true);
            offset += 4;

            // 写入主题
            const topicView = new Uint8Array(buffer, offset, topicBytes.length);
            topicView.set(topicBytes);

            return buffer;
        }

        // 解码二进制消息
        function decodeBinaryMessage(buffer) {
            const view = new DataView(buffer);
            let offset = 0;

            // 读取消息类型 (1字节)
            const type = view.getUint8(offset);
            offset += 1;

            // 读取内容长度 (4字节)
            const contentLength = view.getUint32(offset, true);
            offset += 4;

            // 读取内容
            const contentBytes = new Uint8Array(buffer, offset, contentLength);
            const content = new TextDecoder().decode(contentBytes);
            offset += contentLength;

            // 读取主题长度 (4字节)
            const topicLength = view.getUint32(offset, true);
            offset += 4;

            // 读取主题
            const topicBytes = new Uint8Array(buffer, offset, topicLength);
            const topic = new TextDecoder().decode(topicBytes);

            return {
                type: type,
                content: content,
                topic: topic
            };
        }

        // DOM元素
        const appidSelect = document.getElementById('appid');
        const uidInput = document.getElementById('uid');
        const tokenInput = document.getElementById('token');
        const connectBtn = document.getElementById('connect-btn');
        const disconnectBtn = document.getElementById('disconnect-btn');
        const statusSpan = document.getElementById('status');
        const messageContainer = document.getElementById('message-container');
        const messageInput = document.getElementById('message');
        const messageFormatSelect = document.getElementById('message-format');
        const sendBtn = document.getElementById('send-btn');

        // WebSocket连接
        let socket = null;

        // 心跳定时器
        let heartbeatTimer = null;

        // 心跳间隔（毫秒）
        const HEARTBEAT_INTERVAL = 30000; // 30秒

        // 添加消息到消息容器
        function addMessage(text, type = 'system') {
            const messageDiv = document.createElement('div');
            messageDiv.classList.add('message', type);
            messageDiv.textContent = text;
            messageContainer.appendChild(messageDiv);
            messageContainer.scrollTop = messageContainer.scrollHeight;
        }

        // 连接到WebSocket服务器
        function connect() {
            const appid = appidSelect.value.trim();
            const uid = uidInput.value.trim();
            const token = tokenInput.value.trim();

            if (!appid) {
                alert('请选择应用ID');
                return;
            }

            if (!uid) {
                alert('请输入用户ID');
                return;
            }

            if (!token) {
                alert('请输入Token');
                return;
            }

            try {
                // 创建WebSocket连接
                // 使用当前页面的主机和端口
                const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
                const host = window.location.host;
                socket = new WebSocket(`${protocol}//${host}/ws?appid=${appid}&uid=${uid}&token=${token}`);

                // 设置二进制类型
                socket.binaryType = "arraybuffer";

                // 连接打开时
                socket.onopen = function() {
                    // 更新UI状态
                    connectBtn.disabled = true;
                    disconnectBtn.disabled = false;
                    messageInput.disabled = false;
                    messageFormatSelect.disabled = false;
                    sendBtn.disabled = false;
                    statusSpan.textContent = '已连接';
                    statusSpan.classList.remove('disconnected');
                    statusSpan.classList.add('connected');

                    addMessage(`已连接到服务器，应用ID: ${appid}, 用户ID: ${uid}`);

                    // 订阅news主题（使用二进制格式）
                    const subscribeBuffer = encodeBinaryMessage(MessageType.SUBSCRIBE, "", "news");
                    socket.send(subscribeBuffer);

                    addMessage('已订阅news主题 (使用二进制格式)');

                    // 启动心跳
                    startHeartbeat();
                };

                // 接收消息时
                socket.onmessage = function(event) {
                    try {
                        if (event.data instanceof ArrayBuffer) {
                            // 二进制消息
                            const msg = decodeBinaryMessage(event.data);
                            if (msg.type === MessageType.TEXT) {
                                addMessage(`收到二进制文本消息: ${msg.content}`, 'received');
                            } else if (msg.type === MessageType.PING) {
                                console.log('收到心跳请求，发送响应');
                                // 发送pong响应
                                const pongBuffer = encodeBinaryMessage(MessageType.PONG, "", "");
                                socket.send(pongBuffer);
                            } else if (msg.type === MessageType.PONG) {
                                console.log('收到心跳响应');
                                // 心跳响应不需要特殊处理
                            } else {
                                addMessage(`收到二进制消息: 类型=${msg.type}, 内容=${msg.content}, 主题=${msg.topic}`, 'received');
                            }
                        } else {
                            // JSON消息
                            const msg = JSON.parse(event.data);
                            if (msg.type === 'text') {
                                addMessage(`收到JSON文本消息: ${msg.content}`, 'received');
                            } else {
                                addMessage(`收到JSON消息: ${JSON.stringify(msg)}`, 'received');
                            }
                        }
                    } catch (e) {
                        // 如果解析失败，直接显示
                        addMessage(`收到未知格式消息: ${event.data}`, 'received');
                    }
                };

                // 连接关闭时
                socket.onclose = function(event) {
                    const reason = event.reason || '未知原因';
                    addMessage(`连接已关闭: ${reason} (代码: ${event.code})`);

                    // 更新UI状态
                    connectBtn.disabled = false;
                    disconnectBtn.disabled = true;
                    messageInput.disabled = true;
                    messageFormatSelect.disabled = true;
                    sendBtn.disabled = true;
                    statusSpan.textContent = '未连接';
                    statusSpan.classList.remove('connected');
                    statusSpan.classList.add('disconnected');
                };

                // 连接错误时
                socket.onerror = function(error) {
                    addMessage(`连接错误: ${error.message || '未知错误'}`);
                };
            } catch (e) {
                addMessage(`创建连接时出错: ${e.message}`);
            }
        }

        // 断开WebSocket连接
        function disconnect() {
            if (socket) {
                // 取消订阅news主题（使用二进制格式）
                try {
                    const unsubscribeBuffer = encodeBinaryMessage(MessageType.UNSUBSCRIBE, "", "news");
                    socket.send(unsubscribeBuffer);
                } catch (e) {
                    // 忽略发送错误
                }

                // 关闭连接
                try {
                    socket.close(1000, "用户主动断开连接");
                } catch (e) {
                    // 忽略关闭错误
                }
                socket = null;
            }

            // 更新UI状态
            connectBtn.disabled = false;
            disconnectBtn.disabled = true;
            messageInput.disabled = true;
            messageFormatSelect.disabled = true;
            sendBtn.disabled = true;
            statusSpan.textContent = '未连接';
            statusSpan.classList.remove('connected');
            statusSpan.classList.add('disconnected');
        }

        // 发送消息到服务器
        function sendMessage() {
            if (!socket || socket.readyState !== WebSocket.OPEN) {
                addMessage('未连接到服务器，无法发送消息');
                return;
            }

            const messageText = messageInput.value.trim();
            if (!messageText) {
                return;
            }

            const useBinary = messageFormatSelect.value === 'binary';

            // 创建HTTP请求发送消息到news主题
            // 使用当前页面的主机和端口
            const host = window.location.host;
            const protocol = window.location.protocol;
            fetch(`${protocol}//${host}/broadcast?topic=news${useBinary ? '&binary=true' : ''}`, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({
                    type: 'text',
                    content: messageText
                })
            })
            .then(response => {
                if (response.ok) {
                    addMessage(`发送${useBinary ? '二进制' : 'JSON'}消息到news主题: ${messageText}`, 'sent');
                    messageInput.value = '';
                } else {
                    addMessage(`发送失败: ${response.status} ${response.statusText}`);
                }
            })
            .catch(error => {
                addMessage(`发送错误: ${error.message}`);
            });
        }

        // 自动生成token
        function generateToken() {
            const appid = appidSelect.value;
            const uid = uidInput.value.trim();

            if (!uid) return;

            if (appid === 'app1') {
                // app1: token是uid的反转
                tokenInput.value = uid.split('').reverse().join('');
            } else if (appid === 'app2') {
                // app2: token是"app2-"+uid
                tokenInput.value = 'app2-' + uid;
            }
        }

        // 事件监听
        connectBtn.addEventListener('click', connect);
        disconnectBtn.addEventListener('click', disconnect);
        sendBtn.addEventListener('click', sendMessage);

        // 当应用ID或用户ID变化时，自动生成token
        appidSelect.addEventListener('change', generateToken);
        uidInput.addEventListener('input', generateToken);

        // 按Enter键发送消息
        messageInput.addEventListener('keypress', function(event) {
            if (event.key === 'Enter') {
                sendMessage();
            }
        });

        // 页面加载时
        addMessage('欢迎使用WebSocket二进制客户端（带验证），请输入应用ID、用户ID和Token并连接');
    </script>
</body>
</html>
